﻿using System;
using System.Linq.Expressions;

namespace UnitBox {
	public class InterceptNextStaticCallConfiguration {

		#region instance fields

		private readonly MethodCallExpression _methodCallExpression;
		private readonly ConfigureLaterInterceptionBehaviour _configureLaterInterceptionBehaviour;

		#endregion


		#region constructors

		internal InterceptNextStaticCallConfiguration( MethodCallExpression methodCallExpression, ConfigureLaterInterceptionBehaviour configureLaterInterceptionBehaviour ) {
			if( methodCallExpression == null )
				throw new ArgumentNullException( "methodCallExpression" );
			if( configureLaterInterceptionBehaviour == null )
				throw new ArgumentNullException( "configureLaterInterceptionBehaviour" );

			_methodCallExpression = methodCallExpression;
			_configureLaterInterceptionBehaviour = configureLaterInterceptionBehaviour;
		}

		#endregion


		#region public methods

		/// <summary>
		/// Optionally specify that you want to track interceptions to the configured method, so that you can verify
		/// whether it got invoked in the tested code or not. 
		/// </summary>
		/// <remarks>
		/// TODO(design): does this still hold true for interceptions?
		/// Note that specifying this will hold on to all invocation parameters, so this should never get called
		/// in production code. Doing so will effectively create a memory leak which will hold on to all
		/// intercepted invocation arguments. During testing however this is perfectly acceptable, as it should will only
		/// matter for the lifetime of the test.
		/// </remarks>
		public void Verifiable() {
			// configure the queue to store invoctions to the configured method
			InterceptionQueue.RememberInvocations( _methodCallExpression );
		}


		public void AndInsteadDo( Action invokeWhenIntercepting ) {
			var interceptionBehaviour = new InvokeInsteadInterceptionBehaviour( invokeWhenIntercepting );
			_configureLaterInterceptionBehaviour.ConfigureWithInterceptionBehaviour( interceptionBehaviour );
		}

		#endregion

	}


	public class InterceptNextStaticCallConfiguration<TReturnValue> {

		#region instance fields

		private readonly Expression _methodCallExpression;
		private readonly ConfigureLaterInterceptionBehaviour<TReturnValue> _configureLaterInterceptionBehaviour;

		#endregion


		#region constructors

		internal InterceptNextStaticCallConfiguration( Expression methodCallExpression, ConfigureLaterInterceptionBehaviour<TReturnValue> configureLaterInterceptionBehaviour ) {
			if( methodCallExpression == null )
				throw new ArgumentNullException( "methodCallExpression" );
			if( configureLaterInterceptionBehaviour == null )
				throw new ArgumentNullException( "configureLaterInterceptionBehaviour" );

			_methodCallExpression = methodCallExpression;
			_configureLaterInterceptionBehaviour = configureLaterInterceptionBehaviour;
		}

		#endregion


		#region public methods

		/// <summary>
		/// Optionally specify that you want to track interceptions to the configured method, so that you can verify
		/// whether it got invoked in the tested code or not. 
		/// </summary>
		/// <remarks>
		/// TODO(design): does this still hold true for interceptions?
		/// Note that specifying this will hold on to all invocation parameters, so this should never get called
		/// in production code. Doing so will effectively create a memory leak which will hold on to all
		/// intercepted invocation arguments. During testing however this is perfectly acceptable, as it should will only
		/// matter for the lifetime of the test.
		/// </remarks>
		public void Verifiable() {
			throw new NotImplementedException();
		}


		public void AndInsteadReturn( TReturnValue replacedReturnValue ) {
			var interceptionBehaviour = new ReturnSpecificValueInterceptionBehaviour<TReturnValue>( replacedReturnValue );
			_configureLaterInterceptionBehaviour.ConfigureWithInterceptionBehaviour( interceptionBehaviour );
		}


		public void AndInsteadDo( Func<TReturnValue> invokeWhenIntercepting ) {
			var interceptionBehaviour = new InvokeInsteadInterceptionBehaviour<TReturnValue>( invokeWhenIntercepting );
			_configureLaterInterceptionBehaviour.ConfigureWithInterceptionBehaviour( interceptionBehaviour );
		}

		#endregion

	}
}